<?php
/* Pawel Giemza
 * @version 0.9
 * @package ORM
 * @subpackage Schema
 * @link http://arrowplatform.org/orm
 * @copyright 2011 Arrowplatform 
 * @license GNU LGPL
*/

require_once('../interfaces/IORMBaseQuery.php');
require_once('../datasources/DataSourceManager.php');
/*require_once('../interfaces/IORMSerializable.php'); */
 
class OrmBaseQuery implements /*IORMSerializable,*/ IORMBaseQuery {
  /* Fields */
	/**
	*
	* Array of tuples. 
  *  Each tuple consists of following values:
  * - condition recort type ( I_BASIC_CONDITION, ...)
	* - class name
	* - column name
	* - column value
	* - condition type
	* - condition join type
	* Query can be put to andOr/And method to create a subgroup
	* in such case only two calues will exist in tuple:
	* - Query object
	* - condition join type 
	*
	*/
  protected $conditions = null;
  protected $removedFields = array();
  protected $addedFields = array();
  protected $replacedFields = array();
  protected $basicFields = true;
  protected $returnType = 'data';
  
  protected $model = null;
  protected $class = null;


  const I_LINK_TYPE_OR = 'or';
  const I_LINK_TYPE_AND = 'and';
  
  /* Normal condition using Field, Value, Condition  */
  const I_BASIC_CONDITION = 1;
  /* Conditions enclosed in BaseQuery Object */
  const I_OBJECT_CONDITION = 0;
  /* Conditions using snippets */
  const I_SNIPPET_CONDITION = 2;

	const C_EQUAL = '==';  		/**<Equality condition (SQL: = ) */
	const C_NOT_EQUAL  = '!=';	/**<Inequality condition (SQL: != ) */
	const C_GREATER_EQUAL = '>='; /**<Greater or equal condition (SQL: >= )*/
	const C_GREATER_THAN = '>'; 	/**<Greater than condition (SQL: > )*/
	const C_LESS_EQUAL = '<='; 	/**<Less or equal condition (SQL: <= )*/
	const C_LESS_THAN = '<'; 	/**<Greater than condition (SQL: < )*/

	const C_IN = 'IN';      	/**<In operator (SQL: IN) <br /> Note: this operator requires $value to be an array*/
	const C_NOT_IN  = 'NOT IN'; 	/**<Not In operator (SQL: NOT IN)<br /> Note: this operator requires $value to be an array*/
	const C_BETWEEN  = 'BETWEEN'; 	/**<Between operator (SQL: BETWEEN) <br /> Note: this operator requires $value to be 2 element array*/	

	const C_LIKE  = 'LIKE';   		/**<Like operator (SQL: LIKE)*/
	const C_ILIKE = 'ILIKE';   		/**<Case insensitive Like operator (SQL: ILIKE)*/
	const C_NOT_LIKE  = 'NOT LIKE';	/**<Not Like operator (SQL: NOT LIKE)*/
	const C_NOT_ILIKE  = 'NOT ILIKE';	/**<Case insensitive Not Like operator (SQL: NOT ILIKE)*/	
	
	const C_BIT_OR = '|';	/**<Bit OR */
	const C_BIT_AND = '&';	/**<Bit AND */
	const C_BIT_XOR = '^';	/**<Bit XOR */
	
	const C_RELATION  = '===';/**<Relation operator (SQL: =) <br /> Note: When this operator is used $value will not be escaped with " ' " */
	const C_OR_GROUP = 'OR';	/**<Start nested OR group*/
	const C_AND_GROUP = 'AND';	/**<Start nested AND group*/
	
	const C_CUSTOM = 'CUSTOM';	/**Custom condition*/

  /* Implementation of IArrowBaseQuery interface */
  
  /**
  * 
  * Constructor is protected because we create objects with "start" method
  *
  */
  protected function __construct($field, $value, $condition){
    $this->conditions = array();
     
    //TODO check whether modelname or class name has been delivered.
    $this->model = '_default_';
    $this->class = '_default_';
    
    $this->_and($field, $value, $condition);   
  }
  
  protected function getClassFromModel( $model ) {
  	$ex = explode( '\\', $model ) ;
    return end( $ex ) ;
  }
    
    /**
    *
    *Start method is used to instanciate Query object.
    *
    * string  $model - name of Arrow Model (or class) to be used with this Query
    */
  public static function start($field, $value=0, $condition=self::C_EQUAL){
    $ob = new OrmBaseQuery($field, $value, $condition);
    return $ob;
  }


    /**
    *
    *SetModel method is used to tell Query object which model to use when connecting to datasource
    *
    * string  $model - name of Arrow Model (or class) to be used with this Query
    */    
    public function setModel($model){
      $this->model = $model;
      $this->class = self::getClassFromModel($model);

      //Index in condition array under which classname is stored
      $classIndex = 1;
      //update model unformation in conditions
      foreach ($this->conditions as &$condition){
        if (is_object($condition)) 
          $condition->setModel($this->model);
        else
          if ($condition[$classIndex] == '_default_')
            $condition[$classIndex] = $this->class;
      }
      return $this;
    }
    
    /**
    *
    * Adds new condition to Query Object using 'or' condition to join it with previous conditions.
    *
    * string $field - name of field used in condition
    * mixed $value - value(s) to be used in this condition
    * string $condition - type of contition to use (default is '=' )
    */
    /**
    *
    * Adds group of conditions usinf 'and' to join them with previous conditions
    *
    * BaseQuery $field - query object with conditions
    */
    public function _or( $field, $value = 0, $condition=self::C_EQUAL ){
    $className = $this->class;
    if ( $field instanceof ArrowOrmBaseQuery )
      $this->conditions[] = array(self::I_OBJECT_CONDITION, $className, $field, self::I_LINK_TYPE_OR) ;
    else
      $this->conditions[] = array(self::I_BASIC_CONDITION,  $className, $field, $value, $condition, self::I_LINK_TYPE_OR);
    return $this;
    }
    
    public function _and( $field, $value = 0, $condition=self::C_EQUAL ){
     $className = $this->class;

    if ( $field instanceof ArrowOrmBaseQuery )
      $this->conditions[] = array(self::I_OBJECT_CONDITION, $className, $field, self::I_LINK_TYPE_AND) ;
    else
      $this->conditions[] = array(self::I_BASIC_CONDITION, $className, $field, $value, $condition, self::I_LINK_TYPE_AND);
    return $this;
    }
        
    /**
    *
    * Removes condition with specified id. 
    * If complex id is supplied (eg: "3/2") then condition from subgroup will be remowed
    *
    */  
    public function removeCondition( $id ){
    //TODO:: Complex conditions
    //TODO:: czy powinniśmy ca�kowicie usuwać ten ofset, zmienilo by to numeracje pozosta�ych wpis�w i utrobni�o usuwanie kolejnych warunków.
    if (isset( $this->conditions[$id] ) )
      $this->conditions[$id] = null;
    else 
      throw new Exception ('Incorrect id');
    }
    
    public function  addField( $returnName, $dsName=null, $operation=null ){
    if ($dsName === null) $dsName = $returnName;
    //TODo: check for double additions
        $this->addedFields[] = array($returnName, $dsName, $operation );
      return $this;
    }
    
    public function removeField( $returnName){
    if(!$this->basicFields)
        $this->removedFields[] = array($returnName);
      else
      throw new Exception('Can not remove fields with list already empty');
      return $this;
    }
    
    public function replaceField( $returnName, $dsName, $returnName=null, $operation=null ){
    if(!$this->basicFields) 
      $this->replacedFields[] = array($returnName, $dsName, $returnName, $operation);
    else
      throw new Exception('Can not replace fields with list already empty');
    return $this;   
    }

  public function clearFieldList(){
    $this->basicFields = false;
    return $this;
  }
    
    
    /**
    *
    * Changes returnType from 'data' to 'query'
    *
    */
    public function returnQuery( ){
    $this->returnType = 'query';
    return $this;
    }
    
    /**
    * Executes query
    *
    */
  public function execute(){
   return DataSourceManager::query( $this );
  }

    public function getConditions(){
    return $this->conditions;
    }
    
    public function getFields(){
    return array('added'=>$this->addedFields,'removed'=>$this->removedFields,'replaced'=>$this->replacedFields,'basic'=>$this->basicFields);
    }   
    
    public function getReturnType(){
    return $this->returnType;
    }

  /* Implementation of IArrowSerializable interface */
  
    public function serialize(){
    //TODO:: czy używamy standardowej serializacji
    return serialize($this);
    } 
    
    public function unserialize($data){
    //TODO jaka� magia :)
    //return  unserialize($data); 
    }
	
	public function getModel() {
		return $this->model ;
	}
	
	public function getClass() {
		return $this->class ;
	}
    
}

?>



